﻿using Nemerle.Collections;
using Nemerle.Text;
using Nemerle.Utility;
using Nemerle.ComputationExpressions;
using Nemerle.ComputationExpressions.Async;

using System;
using System.Console;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

using Microsoft.Cci;

using Nemerle2.Compiler;

namespace MetadataReader.CCI
{
  public partial class CciMetadataProvider : IMetadataProvider
  {
    _host          : IMetadataReaderHost;
    _typeTable     : ReadWriteHashtable[uint, CciTypeInfo]    = ReadWriteHashtable();
    _assemblyTable : Hashtable[string, IReferencedAssembly]   = Hashtable();
    _unitTable     : Hashtable[IUnit,  CciReferencedAssembly] = Hashtable(); // Как не крти а надо... может оставим только от IUnit... ну посмотрим
    _lock          : object                                   = object();
    _loadLock      : object                                   = object();

    public Msg : list[Msg] { get; private set; }
    public StandardTypes : StandardTypes { get; private set; }
    
    public this()
    {
      Msg = [];
      _host = ReaderHost(this);
    }


    LoadAssemblyInternal(path : string) : CciReferencedAssembly
    {
      def cciUnit = _host.LoadUnitFrom(path);
      //WriteLine($"  $(Threading.Thread.CurrentThread.ManagedThreadId) $(cciUnit.Name.Value)");
      match (cciUnit)
      {
        | a is IAssembly when a != Dummy.Assembly : object => CciReferencedAssembly(a, this)
        | _                                                => null // TODO: CciDummyAssembly
                                                                 // VladD2: Просто в этой реализации все коллекции будут пустыми, а свойство Msg должно
                                                                 // содержать сообщение о том, что сборка не загружена и почему.
      }
    }
    
    public LoadAssembly(path : string) : IReferencedAssembly
    {
      lock (_loadLock)
      {
        mutable assembly;
        
        if (_assemblyTable.TryGetValue(path, out assembly))
          assembly
        else
        {
          def asm = LoadAssemblyInternal(path);
          
          _unitTable[asm.CciAssembly] = asm;
          _assemblyTable.Add(asm.ImagePath, asm);
          
          asm.Init();
          
          asm
        }
      }
    }
    
    public LoadAssemblies(paths : Seq[string]) : Seq[IReferencedAssembly]
    {
      def load(paths : Seq[string]) : Seq[IReferencedAssembly]
      {
        _assemblyTable.Clear();
        _typeTable.Clear();
        _unitTable.Clear();
        
        //def timer = Stopwatch.StartNew();
        
        def loadedAssemblies = paths.ParallelMap(LoadAssemblyInternal);
        //def loadedAssemblies = paths.Map(LoadAssemblyInternal);
        
        //WriteLine($"Setp 1 loadAssemblyAsync took: $(timer.Elapsed)"); timer.Reset(); timer.Start();

        foreach (a in loadedAssemblies)
          foreach (t in a.CciAssembly.GetAllTypes())
            _ = t.InternedKey;

        //WriteLine($"Setp 2 GetAllTypes()     took: $(timer.Elapsed)"); timer.Reset(); timer.Start();
            
        loadedAssemblies.ParallelForEach(asm => asm.Init());
        //loadedAssemblies.Iter(asm => asm.Init());

        //WriteLine($"Setp 3 Init()            took: $(timer.Elapsed)"); timer.Reset(); timer.Start();
          
        foreach (a in loadedAssemblies)
        {
          _unitTable.Add(a.CciAssembly, a);
          _assemblyTable.Add(a.ImagePath, a);
        }

        //WriteLine($"Setp 4 update loockups   took: $(timer.Elapsed)"); timer.Reset(); timer.Start();
        
        //_ = ReadLine();
        
        // VladD2: TODO: Init the StandardTypes property.
        
        loadedAssemblies.OfType()
      }
      
      lock (_loadLock)
        load(paths)
    }

#pragma warning disable 10003 // is not externally visible and has never been referenced
    public event MsgChanged : EventHandler;
#pragma warning restore 10003

    //private ReprtError(text : string) : void
    //{
    //  def msg = TextMsg(ErrorKind.Error, text, isMarkuped=false);

    //  lock (_lock)
    //    Msg ::= msg;

    //  MsgChanged?.Invoke(this : IMetadataProvider, EventArgs.Empty);
    //}

    /// <summary>
    /// Gets a value indicating whether the TypeInfo is a variant type
    /// </summary>
    private IsVariant(ty : INamedTypeDefinition) : bool
    {
      ty.IsAbstract && IsDefinedAttribute(ty, Constants.VariantAttributeFullName)
    }

    private IsVariantOption(ty : INamedTypeDefinition) : bool
    {
      ty.IsSealed && ty.Attributes.HasAttributes(Constants.ConstantVariantOptionAttributeFullName, Constants.VariantOptionAttributeFullName)
      
    }

    private IsTypeAlias(ty : INamedTypeDefinition) : bool
    {
      ty.IsInterface && ty.Attributes.HasAttribute(Constants.TypeAliasAttributeFullName)
    }

    private IsDefinedAttribute(ty : INamedTypeDefinition, fullName : string) : bool
    {
      def res = ty.Attributes.Any(a => Utils.GetFullName(a.Type)
                                         .Equals(fullName, StringComparison.InvariantCulture));
      res
    }

    internal AddTypeInfos(resType : Seq[INamedTypeDefinition], assembly : CciReferencedAssembly) : array[CciTypeInfo]
    {
      def getTypeInfos(resType, assembly) : array[CciTypeInfo]
      {
        def createTypeInfo(resType : INamedTypeDefinition, assembly : CciReferencedAssembly) : CciTypeInfo
        {
          assert(!(resType is IGenericTypeInstanceReference), "Can't use CreateTypeInfo for generic type references");

          def info =
            if (IsVariant(resType))
              CciVariantTypeInfo(this, resType, assembly)
            else if (IsVariantOption(resType))
              CciVariantTypeInfo.CreateCciVariantOptionTypeInfo(this, resType, assembly)
            else if (IsTypeAlias(resType))
              CciAliasTypeInfo(this, resType, assembly)
            else
              CciTypeInfo(this, resType, assembly);

          info
        }

        def localMap = Hashtable();
        def nestedTypeDeclarators = Hashtable();
        def ret = List();

        {
          mutable nesteds;
          mutable containingType;

          foreach(ty in resType)
          {
            def typeInfo = createTypeInfo(ty, assembly);
            localMap.Add(ty, typeInfo);
            ret.Add(typeInfo);

            match (ty)
            {
              | ty is INestedTypeReference =>
                unless (containingType == ty.ContainingType : object)
                {
                  containingType = ty.ContainingType; // In almost all cases types are in alphabetical order
                  unless (nestedTypeDeclarators.TryGetValue(containingType, out nesteds))
                  {
                    nesteds = List();
                    nestedTypeDeclarators.Add(containingType, nesteds);
                  }
                }
                nesteds.Add(ty);
              | _ => {}
            }
          }
        }

        foreach(kv in nestedTypeDeclarators)
        {
          def containingType = localMap[kv.Key];//if key not found exception than its going something terribly wrong!!
          def nesteds = kv.Value.MapToArray(localMap.get_Item(_));

          containingType.SetNestedTypes(nesteds);
        }

        ret.ToArray()
      }

      def types = getTypeInfos(resType, assembly);

      _typeTable.AddRange(types.MapLazy(t => (t.InternedKey, t)));

      types
    }

    internal GetTypeInfo(ty : ITypeReference) : ITypeInfo
    {
      assert(!(ty is IGenericTypeInstanceReference), "Can't use GetTypeInfo for generic type references, use GetType instead");
      def resolvedType = ty.ResolvedType;
      assert(resolvedType.InternedKey > 0, "ResolvedType окончился недачно и нам вернули Dummy-обект.");
      def resType      = resolvedType :> INamedTypeDefinition;
      def key          = resType.InternedKey;

      mutable res;
      def getTyInfo(key)
      {
        _typeTable.TryGetValue(key, out res)
      }
      def getAssembly(type)
      {
        | ty is INamespaceTypeReference => ty.ContainingUnitNamespace.Unit;
        | ty is INestedTypeReference    => getAssembly(ty.ContainingType)
        | ty is IGenericTypeParameterReference       => 
          getAssembly(ty.DefiningType)
          
        | _  => assert(false, "Да как такое вобще могло произойти??");
      }

      unless (getTyInfo(key))
      {
        def cciAsm = getAssembly(ty);
        _ = LoadAssembly(cciAsm.ResolvedUnit.Location);
        
        assert(getTyInfo(key), $"ReaderHost didn't resolved assembly $cciAsm");//Perhaps we shuld add some DummyType support to Nemerle?
      }

      res
    }

    internal GetType(ty : ITypeReference) : FixedType
    {
      match(ty)
      { //todo functions
        | tp is IGenericTypeParameter => FixedType.TypeParameterInfoRef(CciTypeParameterInfo(this, tp))
        | val when val.TypeCode == PrimitiveTypeCode.Void => FixedType.Void();
        | gt is IGenericTypeInstanceReference =>
          def genT = GetTypeInfo(gt.GenericType);
          def args = gt.GenericArguments.Map(t => GetType(t));
          FixedType.Class(genT, args)

        | arr is IArrayType => FixedType.Array(GetType(arr.ElementType), arr.Rank :> int)
        | _ => 
          foreach (itf in ty.GetType().GetInterfaces())
            Debug.WriteLine(itf);
            
          FixedType.Class(GetTypeInfo(ty), []);
      }
    }
  }
}